/*
 * Copyright 2008 Andrej Herich
 * 
 * http://azetclient.yw.sk/
 * http://azetclient.googlecode.com/
 * 
 * This file is part of AzetClient.
 * 
 * AzetClient is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * AzetClient is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with AzetClient.  If not, see <http://www.gnu.org/licenses/>.
 */

package sk.yw.azetclient.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.log4j.Logger;
import sk.yw.azetclient.azet.AzetMessage;

/**
 *
 * @author error216
 */
public class MessageThread {
    
    private static final Logger logger = Logger.getLogger(MessageThread.class);
    
    private int id;
    private String name;
    private boolean stored = false;
    private ArrayList<Message> messages = new ArrayList<Message>();
    private List<MessageThreadListener> listeners =
            new ArrayList<MessageThreadListener>();
    
    public MessageThread(int id) {
        if (id < 1)
            throw new IllegalArgumentException("id is nonpositive");
        
        this.id = id;
    }
    
    public synchronized void addMessageThreadListener(MessageThreadListener listener) {
        listeners.add(listener);
    }
    
    public synchronized void removeMessageThreadListener(MessageThreadListener listener) {
        if (!listeners.remove(listener)) {
            logger.warn("Unable to remove MessageThreadListener.");
        }
    }
    
    protected void fireMessageAdded(int index, Message message) {
        MessageThreadEvent event = new MessageThreadEvent(this,
                MessageThreadEvent.Type.ADD_MESSAGE, index, message);
        for (MessageThreadListener listener : listeners) {
            listener.messageAdded(event);
        }
    }
    
    protected void fireMessageChanged(int index, Message message) {
        MessageThreadEvent event = new MessageThreadEvent(this,
                MessageThreadEvent.Type.CHANGE_MESSAGE, index, message);
        for (MessageThreadListener listener : listeners) {
            listener.messageChanged(event);
        }
    }
    
    /**
     * Adds message to this thread or replaces last message, if add is possible.
     * 
     * If message thread is empty, message is added. If message thread is not
     * empty and message is reply to last message, message is added. If message
     * is reply to one before last, last message is replaced by new.
     * New message is created with bean.createImmutable(this).
     * 
     * This method is thread safe.
     * 
     * @param  bean  message bean which is used to create new message.
     * @return true, if message was succesfully added, false otherwise.
     */
    public synchronized boolean addMessage(MessageBean bean) {
        if (bean == null)
            throw new IllegalArgumentException("Null pointer in message");
        if (logger.isDebugEnabled()) {
            logger.debug("Calling method addMessagge; MessageThread id: " + id
                    + "; Message id: " + bean.getId());
        }
        
        Message message = bean.createImmutable(this);

        // if we have no messages in thread, add and possibly add possible
        // previous message
        if (messages.isEmpty()) {
            MessageBean previous = message.createPossiblePreviousBean();
            if (previous.getContent() != null) {
                messages.add(previous.createImmutable(this));
            }
            
            messages.add(message);
            if (name == null) {
                name = message.getContent();
            }
            stored = false;

            fireMessageAdded(messages.size() - 1, message);

            logger.debug("Message added succesfully.");
            return true;
        } else {
            logger.debug("This thread is not empty.");
        }

        // if new message is reply to last, add
        if (message.isReplyTo(getLastMessage())) {
            messages.add(message);
            stored = false;

            fireMessageAdded(messages.size() - 1, message);

            logger.debug("Message added succesfully.");
            return true;

        } else if (logger.isDebugEnabled()) {
            // some debugging
            generateIsReplyToFailed(getLastMessage(), message);
        }

        // if new message is reply to one before last, replace last message
        // with new message
        if (messages.size() > 1
                && message.isReplyTo(messages.get(messages.size() - 2))) {
            messages.set(messages.size() - 1, message);
            stored = false;

            fireMessageChanged(messages.size() - 1, message);

            logger.debug("Message added succesfully.");
            return true;

        } else if (logger.isDebugEnabled()) {
            // some debugging
            if (messages.size() <= 1) {
                logger.debug("Thread size is less than or equal to 1.");
            } else {
                generateIsReplyToFailed(messages.get(messages.size() - 2),
                        message);
            }
        }
        
        return false;
    }
    
    /**
     * Checks whether add of message is possible.
     * 
     * This method is thread safe.
     * 
     * @param  bean  message bean, which is to be added
     * @return true, if add is possible, false otherwise
     */
    public synchronized boolean isAddPossible(MessageBean bean) {
        Message message = bean.createImmutable(this);
        return messages.isEmpty()
                || message.isReplyTo(getLastMessage())
                || (messages.size() > 1
                && message.isReplyTo(messages.get(messages.size() - 2)));
    }
    
    // generates debug information
    private void generateIsReplyToFailed(Message lastMessage, Message currentMessage) {
        if (!(lastMessage instanceof AzetMessage)) {
            logger.debug("Last message is not AzetMessage.");
        } else if (!(currentMessage instanceof AzetMessage)) {
            logger.debug("Current message is not AzetMessage.");
        } else {
            AzetMessage last = (AzetMessage) lastMessage;
            AzetMessage current = (AzetMessage) currentMessage;
            StringBuilder builder = new StringBuilder(
                    "Message is not a reply to last message.");

            if (current.getSender() == null) {
                logger.debug("Message sender is null.");
            } else if (!current.getSender().equals(last.getReceiver())) {
                builder.append(" current sender '" + current.getSender().getName()
                        + "' != last receiver '" + last.getReceiver().getName() + "';");
            }

            if (current.getReceiver() == null) {
                logger.debug("Message receiver is null.");
            } else if (!current.getReceiver().equals(last.getSender())) {
                builder.append(" current receiver != last receiver;");
            }

            if (current.getTextOfPrevious() == null) {
                logger.debug("Message textOfPrevious is null.");
            } else if (last.getLastFragment() == null) {
                if (!current.getTextOfPrevious().equals(last.getContent())) {
                    builder.append(" current textOfPrevious '" + current.getTextOfPrevious()
                            + "' != last content '" + last.getContent() + "'");
                }
            } else {
                if (!current.getTextOfPrevious().equals(last.getLastFragment())) {
                    builder.append(" current textOfPrevious '" + current.getTextOfPrevious()
                            + "' != last lastFragment '" + last.getLastFragment() + "'");
                }
            }

            logger.debug(builder.toString());
        }
    }
    
    /**
     * Adds message to this thread without checking anything.
     * 
     * This method is thread safe.
     * 
     * @param  bean  message bean, which is used to create new message.
     */
    public synchronized void forceAddMessage(MessageBean bean) {
        if (bean == null)
            throw new IllegalArgumentException("Null pointer in message");
        if (logger.isDebugEnabled()) {
            logger.debug("Calling method forceAddMessagge; MessageThread id: "
                    + id + "; Message id: " + bean.getId());
        }
        
        Message message = bean.createImmutable(this);

        messages.add(message);
        stored = false;


        fireMessageAdded(messages.size() - 1, message);
    }
    
    /**
     * Replaces last message of this thread with new one.
     * 
     * This method is thread safe.
     * 
     * @param  bean  message bean, which is used to create new message.
     */
    public synchronized void forceReplaceLastMessage(MessageBean bean) {
        if (bean == null)
            throw new IllegalArgumentException("Null pointer in message");
        if (logger.isDebugEnabled()) {
            logger.debug("Calling method forceReplaceLastMessagge; MessageThread id: "
                    + id + "; Message id: " + bean.getId());
        }
        
        Message message = bean.createImmutable(this);

        messages.set(messages.size() - 1, message);
        stored = false;
        
        fireMessageChanged(messages.size() - 1, message);
    }
    
    /**
     * Returns list of messages contained in this thread.
     * 
     * List is unmodifiable.
     * 
     * This method is thread safe.
     * 
     * @return list of messages contained in this thread
     */
    public synchronized List<Message> getMessages() {
        return Collections.unmodifiableList(messages);
    }
    
    /**
     * Returns last message of this thread.
     * 
     * This method is thread safe.
     * 
     * @return last message of this thread
     */
    public synchronized Message getLastMessage() {
        if (messages.isEmpty()) return null;
        return messages.get(messages.size() - 1);
    }
    
    public synchronized boolean isEmpty() {
        return messages.isEmpty();
    }
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof MessageThread) {
            MessageThread thread = (MessageThread) obj;
            return id == thread.getId();
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return id;
    }

    public int getId() {
        return id;
    }

    public synchronized String getName() {
        return name;
    }

    public synchronized void setName(String name) {
        this.name = name;
    }

    public synchronized boolean isStored() {
        return stored;
    }

    public synchronized void setStored(boolean stored) {
        this.stored = stored;
    }
}
